package com.partsoft.dits.ldap;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import com.partsoft.dits.EdgeDescriptor;

/**
 * LDAP上下文描述符
 */
public class LdapContextDescriptor implements EdgeDescriptor, Cloneable, BeanNameAware {
	
	public static int POOL_DEFAULT_MAXACTIVE = 50;

	public static int POOL_DEFAULT_MAXIDLE = 25;

	public static int POOL_DEFAULT_MINIDLE = 5;

	private String id;

	private String title;

	private LdapConntectType conntectType = LdapConntectType.normal;

	private String host;

	private int port = 389;

	private String baseDN;

	private String userDN;

	private String userPassword;
	
	private String validateDN;

	private String description;

	private int poolMaxActive = POOL_DEFAULT_MAXACTIVE;

	private int poolMaxIdle = POOL_DEFAULT_MAXIDLE;

	private int poolMinIdle = POOL_DEFAULT_MINIDLE;
	
	private boolean pooled = false;
	
	private int ordinal = 0;

	private LdapBindAuthenticationType authenticationType = LdapBindAuthenticationType.none;

	public LdapContextDescriptor() {
	}

	public LdapContextDescriptor(String host, int port, LdapConntectType conntectType, String baseDN,
			LdapBindAuthenticationType authenticationType, String userDN, String userPassword, String title,
			String description) {
		this.host = host;
		this.port = port;
		this.conntectType = conntectType;
		this.baseDN = baseDN;
		this.authenticationType = authenticationType;
		this.userDN = userDN;
		this.userPassword = userPassword;
		this.title = title;
		this.description = description;
	}

	public LdapContextDescriptor(String host, String baseDN) {
		this(host, 389, LdapConntectType.normal, baseDN, LdapBindAuthenticationType.none, null, null, null, null);
	}

	public LdapContextDescriptor(String host, String baseDN, String userDN, String userPassword) {
		this(host, 389, LdapConntectType.normal, baseDN, LdapBindAuthenticationType.simple, userDN, userPassword, null,
				null);
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public void setBeanName(String beanName) {
		setId(beanName);
	}

	@Override
	public LdapContextDescriptor clone() {
		try {
			return (LdapContextDescriptor) super.clone();
		} catch (CloneNotSupportedException e) {
			return null;
		}
	}

	public String getTitle() {
		return title == null ? "" : title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getDescription() {
		return description == null ? "" : description;
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}
	
	public int getOrdinal() {
		return ordinal;
	}

	public void setOrdinal(int ordinal) {
		this.ordinal = ordinal;
	}

	public LdapBindAuthenticationType getAuthenticationType() {
		return authenticationType == null ? getAuthenticationType() : authenticationType;
	}

	public void setAuthenticationType(LdapBindAuthenticationType authenticationType) {
		this.authenticationType = authenticationType;
	}

	public String getBaseDN() {
		return baseDN != null ? baseDN : "";
	}

	public void setBaseDN(String baseDN) {
		this.baseDN = baseDN;
	}

	public String getUserDN() {
		return userDN == null ? "" : userDN;
	}

	public void setUserDN(String userDN) {
		this.userDN = userDN;
	}

	public String getUserPassword() {
		return userPassword == null ? "" : userPassword;
	}

	public void setUserPassword(String userPassword) {
		this.userPassword = userPassword;
	}

	public LdapConntectType getConntectType() {
		return conntectType == null ? LdapConntectType.normal : conntectType;
	}

	public void setConntectType(LdapConntectType conntectType) {
		this.conntectType = conntectType;
	}

	public String getConnectURL() {
		StringBuilder sbf = new StringBuilder();
		sbf.append(getConntectType().getConnectPrefix()).append(getHost()).append(":").append(getPort()).append("/");
		return sbf.toString();
	}

	public String getLdapProviderURL() {
		StringBuilder sbf = new StringBuilder();
		sbf.append(getConntectType().getConnectPrefix()).append(getHost()).append(":").append(getPort()).append("/")
				.append(getBaseDN());
		return sbf.toString();
	}

	private Hashtable<?, ?> createContextEnvironment() {
		Hashtable<String, String> env = new Hashtable<String, String>(5);
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, getLdapProviderURL());
		if (getConntectType() == LdapConntectType.ssl) {
			env.put(Context.SECURITY_PROTOCOL, getConntectType().name());
		} else if (getConntectType() == LdapConntectType.tls) {
			// TODO 需要实现tls安全连接方式
			throw new IllegalArgumentException("目前不支持TLS安全连接方式");
		}
		if (getAuthenticationType() == LdapBindAuthenticationType.simple) {
			env.put(Context.SECURITY_AUTHENTICATION, getAuthenticationType().name());
			env.put(Context.SECURITY_PRINCIPAL, getUserDN());
			env.put(Context.SECURITY_CREDENTIALS, getUserPassword());
		} else if (getAuthenticationType() == LdapBindAuthenticationType.none) {
			env.put(Context.SECURITY_AUTHENTICATION, getAuthenticationType().name());
		} else {
			// TODO 需要实现其他方式
			throw new IllegalArgumentException("目前仅支持simple验证绑定方式");
		}
		return env;
	}
	
	public boolean isPooled() {
		return pooled;
	}
	
	public void setPooled(boolean pooled) {
		this.pooled = pooled;
	}

	public LdapContext createLdapContext() throws NamingException {
		return new InitialLdapContext(createContextEnvironment(), null);
	}
	
	public LdapContextSource createContextSource() {
		LdapContextSource lcs = new LdapContextSource();
		lcs.setBase(getBaseDN());
		if (StringUtils.hasText(getUserDN()) && StringUtils.hasText(getUserPassword())) {
			lcs.setAnonymousReadOnly(false);
			lcs.setUserDn(getUserDN());
			lcs.setPassword(getUserPassword());
		} else {
			lcs.setAnonymousReadOnly(true);
		}
		lcs.setUrl(getConnectURL());
		lcs.setPooled(isPooled());
		try {
			lcs.afterPropertiesSet();
		} catch (Throwable e) {
			throw new IllegalArgumentException(e.getMessage(), e);
		}
		return lcs;
	}	

	public void setDescription(String description) {
		this.description = description;
	}

	public int getPoolMaxActive() {
		return poolMaxActive < 1 ? POOL_DEFAULT_MAXACTIVE : poolMaxActive;
	}

	public void setPoolMaxActive(int poolMaxActive) {
		this.poolMaxActive = poolMaxActive;
	}

	public int getPoolMaxIdle() {
		return poolMaxIdle < 1 ? POOL_DEFAULT_MAXIDLE : poolMaxIdle;
	}

	public void setPoolMaxIdle(int poolMaxIdle) {
		this.poolMaxIdle = poolMaxIdle;
	}

	public int getPoolMinIdle() {
		return poolMinIdle < 1 ? POOL_DEFAULT_MINIDLE : poolMinIdle;
	}

	public void setPoolMinIdle(int poolMinIdle) {
		this.poolMinIdle = poolMinIdle;
	}
	
	public boolean isWriteable() {
		return StringUtils.hasText(getUserDN()) && StringUtils.hasText(getUserPassword());
	}

	@Override
	public String toString() {
		StringBuffer sbf = new StringBuffer("LDAP连接");
		sbf.append("[");
		sbf.append("ID=").append(getId()).append(";");
		sbf.append("主机=").append(getHost()).append(";端口=").append(getPort());
		sbf.append("]");
		return sbf.toString();
	}
	
	public String getValidateDN() {
		return validateDN == null ? "" : validateDN;
	}

	public void setValidateDN(String validateDN) {
		this.validateDN = validateDN;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!ClassUtils.isAssignableValue(getClass(), obj))
			return false;
		LdapContextDescriptor other = (LdapContextDescriptor) obj;
		if (id == null) {
			if (other.getId() != null)
				return false;
		} else if (!id.equals(other.getId()))
			return false;
		return true;
	}

}
